feat(objc): Add SentryObjC wrapper SDK#7598
Conversation
Add a pure Objective-C SDK wrapper that mirrors the Sentry public API so it can be used from ObjC++ without modules (e.g. for React Native, Haxe, custom build systems). Includes: - SentryObjC product and target in Package.swift - Re-declared headers for all public types in Sources/SentryObjC/Public/ - Wrapper implementations for Swift-only types (Unit, Metric, MetricValue, AttributeContent, RedactRegionType) - iOS-ObjectiveCpp-NoModules sample using SentryObjC - sdk_objc_api.json generation via extract-objc-api.py for API stability CI - Makefile targets: build-spm-objc, verify-objc, generate-objc-api Refs GH-6342
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Documentation 📚
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
|
Add detailed documentation to core SentryObjC public APIs following Objective-C documentation best practices: - Document all properties with their purpose and behavior - Document all methods with parameters and return values - Add class-level documentation explaining usage context - Include notes about automatic behavior and warnings where applicable Files documented: - SentryObjCSDK: Main SDK entry point - SentryObjCOptions: Configuration options - SentryObjCEvent: Event data structure - SentryObjCScope: Contextual data container - SentryObjCBreadcrumb: Breadcrumb trail - SentryObjCUser: User identification - SentryObjCSpanProtocol: Performance tracing protocol Also adds changelog entry explaining the purpose of the SentryObjC wrapper SDK.
Add comprehensive documentation to exception, attachment, message, and tracing-related classes: - SentryObjCException: Exception information - SentryObjCAttachment: File attachments - SentryObjCMessage: Log messages - SentryObjCSpanContext: Span trace context - SentryObjCTransactionContext: Transaction context All classes now include detailed property and method documentation following Objective-C best practices.
Add comprehensive documentation to: - SentryObjCFrame: Stack frame with source location and context - SentryObjCStacktrace: Stack trace with frames and registers - SentryObjCThread: Thread information and crash state - SentryObjCMechanism: Error mechanism and handling context - SentryObjCDebugMeta: Debug symbols and binary metadata - SentryObjCRequest: HTTP request information - SentryObjCGeo: Geographical location data All classes now include detailed property and method documentation.
Add comprehensive documentation to: - SentryObjCReplayOptions: Session replay configuration with privacy controls - SentryObjCReplayApi: Runtime replay control and masking API - SentryObjCSamplingContext: Context for dynamic trace sampling decisions All properties and methods now include detailed documentation explaining their purpose, behavior, and usage.
Remove the conditional check for sdk_objc_api.json existence since an empty baseline file will be added to main in a follow-up PR. This simplifies the workflow logic.
Keep the original problem description explaining why modules don't work in ObjC++ projects, then show how SentryObjC solves this issue. This provides better context for readers to understand why SentryObjC exists. - Restore "Problem" section describing module import failures - Add "Solution: SentryObjC" section explaining how it solves the issue - List key differences from the main Sentry framework - Reference original issue #4543 and solution PR #6342
Sync with main to include latest changes and adopt new sample structure using projectReferences instead of SPM packages.
Replace Python-based regex parsing with clang AST dump and jq processing. The new approach: - Uses xcrun clang -ast-dump=json for reliable parsing - Extracts declarations via jq queries on intermediate files - Outputs structured JSON objects instead of string signatures - Migrates from Python to pure bash/jq pipeline Also standardize filename to sdk_api_objc.json to match the sdk_api.json and sdk_api_sentryswiftui.json naming convention. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Include the iOS-ObjectiveCpp-NoModules sample in CI build verification to ensure it continues to build successfully with the SentryObjC wrapper. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add the SentryObjC product and target to Package@swift-6.1.swift so the sample can reference it. Also revert iOS-ObjectiveCpp-NoModules sample to use SPM packages instead of projectReferences since SentryObjC is an SPM product, not an Xcode project target. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Complete documentation for: - SentryObjCAppStartMeasurement (app start types and timestamps) - SentryObjCAttributeContent (typed attribute values) - SentryObjCBaggage (distributed tracing propagation) - SentryObjCError (SDK error codes and helpers) - SentryObjCLogger (structured logging interface) - SentryObjCMechanismContext (crash metadata) - SentryObjCMetric (custom performance metrics) - SentryObjCMetricValue (metric value types) - SentryObjCNSError (serializable error representation) All public types, properties, methods, and enum values now have comprehensive documentation following Objective-C best practices. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
@sentry review |
📲 Install BuildsiOS
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7814719 | 1220.98 ms | 1254.14 ms | 33.16 ms |
| eddca8a | 1226.17 ms | 1259.98 ms | 33.81 ms |
| 85ee155 | 1227.40 ms | 1251.52 ms | 24.12 ms |
| a781a3d | 1210.98 ms | 1244.31 ms | 33.33 ms |
| c67ced3 | 1223.22 ms | 1256.15 ms | 32.93 ms |
| 29d546e | 1224.06 ms | 1257.05 ms | 32.98 ms |
| bdd8e0e | 1233.35 ms | 1266.96 ms | 33.60 ms |
| 1155f0c | 1222.41 ms | 1251.18 ms | 28.76 ms |
| 7d833a3 | 1226.72 ms | 1263.42 ms | 36.70 ms |
| 86bd73c | 1232.52 ms | 1269.53 ms | 37.01 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7814719 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| eddca8a | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 85ee155 | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| a781a3d | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| c67ced3 | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 29d546e | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| bdd8e0e | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 1155f0c | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 7d833a3 | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 86bd73c | 24.14 KiB | 1.16 MiB | 1.13 MiB |
Previous results on branch: philprime/objc-wrapper-sdk-6342
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| fd98cd2 | 1221.76 ms | 1255.34 ms | 33.58 ms |
| 39908fa | 1216.45 ms | 1247.67 ms | 31.22 ms |
| 1db3ef0 | 3762.07 ms | 3780.62 ms | 18.56 ms |
| f042c2d | 1217.49 ms | 1232.57 ms | 15.08 ms |
| a1c7a37 | 1218.20 ms | 1247.48 ms | 29.28 ms |
| 3d2e8a4 | 1235.17 ms | 1264.59 ms | 29.42 ms |
| 14dc1d7 | 1207.65 ms | 1228.72 ms | 21.08 ms |
| a1f5242 | 1222.28 ms | 1245.53 ms | 23.25 ms |
| 31709d3 | 1223.00 ms | 1255.70 ms | 32.70 ms |
| 694c3ad | 1225.69 ms | 1250.48 ms | 24.79 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| fd98cd2 | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| 39908fa | 24.14 KiB | 1.13 MiB | 1.11 MiB |
| 1db3ef0 | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| f042c2d | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| a1c7a37 | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 3d2e8a4 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 14dc1d7 | 24.14 KiB | 1.13 MiB | 1.11 MiB |
| a1f5242 | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 31709d3 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 694c3ad | 24.14 KiB | 1.15 MiB | 1.13 MiB |
The SentryObjC product depends on SentryCppHelper, but it was only defined in the binary targets section. Add it to the compile-from-source targets array so it's available when building SentryObjC from source. Fixes SPM package resolution error: target 'SentryCppHelper' referenced in product 'SentryObjC' could not be found Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add SentryObjCSDK.m implementation that forwards all method calls to SentrySDKInternal. This enables SentryObjC to work in projects with CLANG_ENABLE_MODULES=NO, where Swift's @objc bridging is unavailable. The wrapper provides a pure Objective-C implementation of the SentrySDK class, making all SDK methods accessible via #import <SentryObjC.h> without requiring modules or Swift bridging. Update iOS-ObjectiveCpp-NoModules sample to use the corrected import style (#import <SentryObjC.h> instead of <SentryObjC/SentryObjC.h>) and add module.modulemap for SPM module resolution. Refs #6342
Sentry Build Distribution
|
SentryCppHelper must remain in the initial targets array because the binary distribution products (Sentry, SentrySwiftUI, etc.) depend on it. Moving it to the compile-from-source section broke those binary products. The target can be safely referenced by both binary and source products.
7abca39 to
913cdfd
Compare
Sentry Build Distribution
|
Keep unbeautified xcodebuild output in slice job logs. Print archive diagnostics for each produced slice so successful producer jobs expose emitted architectures and Swift module files.
Archive macOS slices with a generic destination so Xcode builds all valid architectures instead of selecting the runner's My Mac arch. Make standalone SentryObjC cleanup idempotent after removing its temp directory during the slice loop.
Log effective xcodebuild settings before each slice build and print final archive metadata after the slice is produced. Build xcodebuild commands from arrays so optional destination args do not trip nounset when they are empty.
…r-sdk-6342 # Conflicts: # agents.toml # scripts/build-xcframework-slice.sh # scripts/compress-xcframework.sh
Add "Get version" step matching the variant workflow so the release-version input is consumed into VERSION env.
The merge conflict resolution dropped the destination_args that ensure macOS slices build for all architectures.
Avoids unbound variable error with empty bash arrays under set -u on macOS system bash.
Revert to the working version from before the merge with origin/main. Uses array-based xcodebuild args which avoids the empty array unbound variable issue.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 18e1f5f. Configure here.
Combine main's logging/structure improvements with array-based xcodebuild args from this branch.
| } | ||
| } | ||
|
|
||
| #if canImport(UIKit) && !os(visionOS) |
There was a problem hiding this comment.
Bug: The Swift conditional compilation guard is missing the !SENTRY_NO_UI_FRAMEWORK check, causing it to diverge from the corresponding Objective-C protocol guard, which can lead to build failures.
Severity: MEDIUM
Suggested Fix
Align the Swift conditional compilation guard with the Objective-C guard. Update the guard in SentryObjCBridgeCallbacks.swift to include a check for !SENTRY_NO_UI_FRAMEWORK, making it consistent with the protocol declaration in SentryObjCBridging.h.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: Sources/SentryObjCBridge/SentryObjCBridgeCallbacks.swift#L23
Potential issue: There is a mismatch between the conditional compilation guards for
session replay callbacks in Swift and Objective-C. The Swift code is guarded by `#if
canImport(UIKit) && !os(visionOS)`, while the Objective-C protocol declaration is
guarded by `#if (TARGET_OS_IOS || TARGET_OS_TV) && !SENTRY_NO_UI_FRAMEWORK`. When
building with the `SENTRY_NO_UI_FRAMEWORK` flag on iOS or tvOS, the Swift bridge will
compile methods that are not declared in the Objective-C protocol. This violates the
bridging contract, can cause protocol conformance build failures in Xcode, and results
in dead code being included in the final binary.
Also affects:
Sources/SentryObjCTypes/Public/SentryObjCBridging.h:159~162Sources/SentryObjC/SentrySDKCallbackBridge.m:34~38Sources/SentryObjC/Public/SentryObjCDefines.h:50~52
|
|
||
| static const void *kBeforeSendMetricKey = &kBeforeSendMetricKey; | ||
|
|
||
| @implementation SentryOptions (ObjCBridge) |
There was a problem hiding this comment.
l: To make it obvious this file is a category extension, maybe it's worth renaming this file to SentryOptions+ObjCBridge.m? (if you agree, SentryReplayObjCBridge.m could also be renamed)
noahsmartin
left a comment
There was a problem hiding this comment.
Sorry to jump in with little context on this history of this PR, but as the author of the issue this is aimed at fixing I felt like I had to provide some context...
As written the PR doesn't really implement what was proposed in the issue. The referenced issue (#6342) shows the objc version of the SDK starts with import Sentry. That's because it's proposing a new SDK that wraps the existing Sentry SDK. What this PR does though is something quite different, it introduces a new target that compiles the same source files as the current SDK but with different settings. IMO this is going to be much more difficult to maintain than what I proposed, and in particular make it harder to migrate more of the SDK to Swift.
An ObjC wrapper SDK would involve 0 changes to existing code files (objc and swift files) and just add one new target to the Package.swift file (SentryObjC - with a dependency on SentrySPM). I would really encourage trying this option since it will make long term maintenance much easier. This PR entangles the two distributions (swift public API and ObjC API) and my suggestion separates these two concerns into distinct layers where the ObjC API builds on the Swift API. A layered approach like that with a clear boundary between the two is usually easier to work with than try to support two modes like this PR is doing.
|
Thanks for the feedback @noahsmartin, I hope I can give more context like this.
I believe you and me have to different takes on the whole topic of building an Objective-C SDK. The goal of this PR is introducing an Objective-C SDK which wraps our existing Swift+ObjC SDK, to make our Swift-only features available to ObjC too and so we eventually can do breaking changes when turning it into a mostly-Swift SDK. By asking SDK users to switch from As Objective-C is the past and Swift is the future, my approach sets focus on refactoring the main SDK to Swift, but still offering an (backwards-compatibility) adapter for Objective-C++ projects.
I believe this was already discovered and discussed in this PR comment. I originally built this wrapper SDK to use the same naming too, so it's a direct drop-in replacement. I assumed that the embedded
Since
This claim is only partially true, because it introduces three new targets ( What the PR actually does is create a pure Objective-C wrapper layer around the existing Swift SDK. The new code acts as a facade/bridge so that ObjC/ObjC++ consumers who can't use Clang modules can still access the full SDK API. The new targets depend on the existing SDK but don't recompile it, they call into it via @objc bridge methods and re-export its existing pure-ObjC headers.
The maintenance overhead is real, I agree with you there, especially with the hand-written ObjC headers that need to stay in sync with the Swift SDK. But the alternative proposal would face the same bridging complexity for any Swift-only type (structs, enums with associated values, generics). The Metrics API, Logger API, and SentryAttributeContent all require manual ObjC-compatible wrappers regardless of architecture. Regarding making it harder to migrate, I must disagree, as my approach decouples ObjC consumers from the internal SDK implementation as described above. If we migrate an internal ObjC type to a Swift struct or enum, only the bridge mapping code needs updating, the consumer-facing ObjC headers don't change.
I have to disagree. The Metrics API is written in pure Swift using structs and generics. It is not compatible with Objective-C and will never be, hence the bridging logic which introduces an Objective-C compatible API. This is the main reason why we are building an Objective-C SDK right now, because sentry-unreal needs it.
I don't believe this is is correct with regards to this pull request. We are only reusing the public ObjC headers to reduce code duplication. We use
My architecture is also creating two distinct layers (with a helper layer in-between): an outer Objective-C SDK wrapping a bridging layer wrapping the Swift(+ObjC) SDK. The boundary is enforced at the module level when I hope this clear things up and that this is not blocking a soonish merge and release. |
🚨 Detected changes in high risk code 🚨High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:
|
| + (SentryFeedbackAPI *)feedback | ||
| { | ||
| return [SentryObjCBridge sdkFeedback]; | ||
| } |
There was a problem hiding this comment.
Bug: The sdkFeedback property is missing from the SentryObjCBridging protocol, bypassing the intended contract verification between the Objective-C and Swift bridge.
Severity: LOW
Suggested Fix
Add the sdkFeedback class property to the SentryObjCBridging protocol in SentryObjCBridging.h, enclosed in the same preprocessor guards (#if TARGET_OS_IOS && SENTRY_HAS_UIKIT) used elsewhere. This will make the protocol accurately reflect the bridging API and allow the compiler to enforce conformance.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: Sources/SentryObjC/SentryObjCSDK.m#L212-L215
Potential issue: The `SentryObjCBridging.h` protocol, which is intended to be the single
source of truth for the Objective-C to Swift bridge, is missing the declaration for the
`sdkFeedback` property. The Objective-C code in `SentryObjCSDK.m` currently relies on a
forward-declaration to access the Swift implementation. This bypasses the protocol's
contract verification, meaning the compiler does not check for the presence of
`sdkFeedback` via protocol conformance. This creates a maintenance risk and allows for
potential drift between the Objective-C call sites and the Swift implementation,
violating the documented architectural pattern.
Also affects:
Sources/SentryObjCTypes/Public/SentryObjCBridging.h:1~172Sources/SentryObjCBridge/SentryObjCBridge.swift:289~293
|
Thanks @philprime I believe we are in alignment with all the technical points it's just the public API and some subjective decisions around architecture that we don't have the same opinions on. Concretely here is how I see the differences in our approach: My proposal is to have a wrapper SDK that only exposes an objc API. The architecture would be like this: In reality the And targets like this: Your proposal does not include a "wrapper" SDK in the traditional sense. It does not wrap the existing SDK, it creates a new library that includes the existing targets (seen in your changes to Package.swift). It also would create a new "mode" of compiling the SDK where the module name would be changed to These are different approaches to solve the same problem. However, I would suggest that the method I'm proposing would be easier to understand (in fact it sounds a lot like what you proposed in the PR description), easier to maintain, and easier for users. The key technical differences would be that my proposal is entirely a wrapper - no changes would be made to existing source files. You could validate this by making my proposal in an entirely new GitHub repo, the resulting framework would be identical to the one you'd make if you implemented my proposal in this repo. I am not proposing a new repo (too much maintenance burden). Rather just a thought experiment to demonstrate some clear separation of concerns. This is why I believe (subjectively) that my proposal would be easier, it provides clear boundaries between systems which generally makes it easier to work with the codebase. I would defer to @itaybre and @philprime to evaluate the subjective pros/cons of these approaches. All I ask that if we go with the library that provides both objc and swift targets in one, we keep #6342 open as a feature request to provide a target that only has publicly visible objc code, with no swift module and no auto-generated objc code from swift |

📜 Description
Adds a pure Objective-C SDK wrapper (
SentryObjC) that enables Sentry usage in Objective-C++ projects withCLANG_ENABLE_MODULES=NO. This solves the long-standing issue where Swift SDK APIs are inaccessible without Clang modules, affecting React Native, Haxe, and other custom build systems.Read the develop-docs/SENTRY-OBJC.md architecture document first.
💡 Motivation and Context
Closes #6342
The Problem
Since SDK 8.54.0, the Swift SDK's public API requires Clang modules to be imported. When
CLANG_ENABLE_MODULES=NO:#import <Sentry/Sentry.h>only exposes Objective-C APIsSentrySDK,SentryOptions.sessionReplay, and the Metrics API are unavailableuse of undeclared identifier 'SentrySDK'This breaks projects that cannot enable modules, particularly:
Implementation Approach & Thought Process
Initial Considerations
Option 1: Make Swift SDK importable without modules
Option 2: Pure ObjC wrapper (chosen approach)
Architecture Decision
The wrapper uses a three-layer architecture:
Why this design?
See
develop-docs/SENTRY-OBJC.md📝 Checklist
🔗 Related Issues & Context